﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using ICeFramework.Common.DataLayer.Interfaces;
using ICeFramework.Common.DataLayer;
using ICeFramework.Common.Configuration;
using ICeFramework.Common.DataLayer.Enums;
using ICeFramework.Common.DataLayer.Exceptions;
using ICeFramework.DataAccess.DataLayer.MsSQL;
using System.Web;
using System.Web.UI;
using System.Reflection;
using System.Collections;

namespace ICeFramework.Business.DataLayer
{
    public abstract class EntityBase<TEntity, TEntityRow> : IDisposable, IEntity
        where TEntity : class
        where TEntityRow : class
    {
        public virtual event EventHandler DataTableChanged;
        public virtual event RowValidationDelegate CustomRowValidation;

        private Type _child;
        private EntityRowErrorCollection _customErrors = new EntityRowErrorCollection();

        protected IDataAccessService _dataAccessService;
        public IDataAccessService DataAccessService
        {
            get { return _dataAccessService; }
            set { _dataAccessService = value; }
        }

        #region public abstract EntityRowCollection<TEntity, TEntityRow> Rows
        /// <summary>
        /// Gets the Rows of the EntityBase
        /// </summary>
        /// <value></value>
        public abstract EntityRowCollection<TEntity, TEntityRow> Rows
        {
            get;
        }
        #endregion

        private EntityColumnCollection<TEntity, TEntityRow> _columns;
        #region public EntityColumnCollection<TEntity, TEntityRow> Columns
        /// <summary>
        /// Gets the Columns of the EntityBase
        /// </summary>
        /// <value></value>
        public EntityColumnCollection<TEntity, TEntityRow> Columns
        {
            get { return _columns; }
        }
        #endregion

        private DataTable _dataTable;
        #region public DataTable DataTable
        /// <summary>
        /// Gets the DataTable of the EntityBase
        /// </summary>
        /// <value></value>
        public DataTable DataTable
        {
            get { return _dataTable; }
        }
        #endregion

        private string _entityName;
        #region public string EntityName
        /// <summary>
        /// Gets the Name of the EntityBase
        /// </summary>
        /// <value></value>
        public string EntityName
        {
            get { return _entityName; }
        }
        #endregion

        #region public virtual bool IsValid
        /// <summary>
        /// Gets the IsValid of the EntityBase
        /// </summary>
        /// <value></value>
        public virtual bool IsValid
        {
            get { return GetErrors().Count == 0; }
        }
        #endregion

        #region public IEntityObject this[int rowIndex, int columnIndex]
        /// <summary>
        /// Gets the <see cref="IEntityObject"/> item identified by the given arguments of the EntityBase
        /// </summary>
        /// <value></value>
        public IEntityObject this[int rowIndex, int columnIndex]
        {
            get { return (Rows[rowIndex] as EntityRowBase<TEntity, TEntityRow>)[columnIndex]; }
        }
        #endregion

        #region public IEntityRow this[int rowIndex]
        /// <summary>
        /// Gets the <see cref="IEntityRow"/> item identified by the given arguments of the EntityBase
        /// </summary>
        /// <value></value>
        public TEntityRow this[int rowIndex]
        {
            get { return Rows[rowIndex] as TEntityRow; }
        }
        #endregion

        #region public EntityBase(Type child)
        /// <summary>
        /// Initializes a new instance of the <b>EntityBase&lt;TEntity, TEntityRow&gt;</b> class.
        /// </summary>
        /// <param name="child"></param>
        public EntityBase(Type child)
            : this(child, string.Empty)
        {

        }
        #endregion

        #region public EntityBase(Type child, string connectionName)
        /// <summary>
        /// Initializes a new instance of the <b>EntityBase&lt;T&gt;</b> class.
        /// </summary>
        /// <param name="child"></param>
        /// <param name="connectionName"></param>
        public EntityBase(Type child, string connectionName)
        {
            switch (ConfigurationHelper.GetServerType())
            {
                case ServerType.MsSql:
                    _dataAccessService = new MsSQLDataAccessService(connectionName);
                    break;
                case ServerType.MySql:
                    throw new NotImplementedException();
                    break;
                case ServerType.Remote:
                    string url = ConfigurationHelper.GetRemoteUrl(connectionName);
                    if (string.IsNullOrEmpty(url))
                    {
                        throw new DataAccessException("The remote url is empty !");
                    }
                    _dataAccessService = new MsSQLDataAccessService(new Uri(url), ConfigurationHelper.GetRemoteUserName(connectionName), ConfigurationHelper.GetRemotePassword(connectionName));
                    break;
                default:
                    throw new DataAccessException("There are no connections (local or remote) properly defined.");
            }
            _dataTable = new DataTable();
            _child = child;
            _dataTable.TableName = GetTableName();
            _entityName = _dataTable.TableName;
            _dataTable.RowChanged += new DataRowChangeEventHandler(_dataTable_RowChanged);
            if (string.IsNullOrEmpty(_dataTable.TableName))
            {
                throw new DataAccessException("TableName is not defined");
            }
            _columns = new EntityColumnCollection<TEntity, TEntityRow>(this);
            SetValidationAttributes();

            if (HttpContext.Current != null)
            {
                _webForm = HttpContext.Current.Handler as Page;
                _webForm.Load += new EventHandler(_webForm_Load);
                _webForm.PreRender += new EventHandler(_webForm_PreRender);
            }
        }
        #endregion

        #region public void Refresh()
        /// <summary>
        /// 
        /// </summary>
        public void Refresh()
        {
            Rows.Clear();
            foreach (DataRow dr in _dataTable.Rows)
            {
                TEntityRow eRow = (TEntityRow)Activator.CreateInstance(typeof(TEntityRow), new object[] { this, dr });
                Rows.Add(eRow);
            }
        }
        #endregion

        #region private void SetValidationAttributes()
        /// <summary>
        /// 
        /// </summary>
        private void SetValidationAttributes()
        {
            PropertyInfo[] properties = this.GetType().GetProperties();
            foreach (PropertyInfo p in properties)
            {
                if (p.PropertyType == typeof(EntityColumn))
                {
                    EntityColumn bc = (EntityColumn)p.GetValue(this, null);
                    bc.Validation = GetValidationSpecs(p);
                    if (p.CanWrite)
                    {
                        p.SetValue(this, bc, null);
                    }
                }
            }
        }
        #endregion

        #region private ValidationFlags GetValidationSpecs(PropertyInfo pInfo)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="pInfo"></param>
        /// <returns></returns>
        private ValidationFlags GetValidationSpecs(PropertyInfo pInfo)
        {
            ValidationAttribute[] validations = (ValidationAttribute[])pInfo.GetCustomAttributes(typeof(ValidationAttribute), true);
            if (validations.Length == 0)
            {
                throw new Exception("Invalid validation");
            }
            return validations[0].ValidationType;
        }
        #endregion

        #region internal void RemoveDataTableRow(object item)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        internal void RemoveDataTableRow(object item)
        {
            EntityRowBase<TEntity, TEntityRow> row = (EntityRowBase<TEntity, TEntityRow>)item;
            _dataTable.Rows.Remove(row.DataRow);
        }
        #endregion

        #region internal TEntityRow AddDataTableRow()
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public TEntityRow NewRow()
        {
            DataRow row = _dataTable.NewRow();
            _dataTable.Rows.Add(row);
            TEntityRow item = (TEntityRow)Activator.CreateInstance(typeof(TEntityRow), this, row);
            return item;
        }
        #endregion

        #region IEntityRow IEntity.NewRow()
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        IEntityRow IEntity.NewRow()
        {
            return NewRow() as IEntityRow;
        }
        #endregion

        #region void _dataTable_RowChanged(object sender, DataRowChangeEventArgs e)
        /// <summary>
        /// This method is called when the _dataTable's RowChanged event has been fired.
        /// </summary>
        /// <param name="sender">The <see cref="object"/> that fired the event.</param>
        /// <param name="e">The <see cref="DataRowChangeEventArgs"/> of the event.</param>
        void _dataTable_RowChanged(object sender, DataRowChangeEventArgs e)
        {

        }
        #endregion

        #region public void Dispose(bool disposing)
        /// <summary>
        /// Releases the resources used by the <b>EntityBase&lt;TEntity, TEntityRow&gt;</b>.
        /// </summary>
        /// <param name="disposing">Set to <b>true</b> to release both managed and unmanaged resources; <b>false</b> to release only unmanaged resources.</param>
        public void Dispose(bool disposing)
        {
            if (disposing)
            {
                _dataAccessService.Connection.Close();
                _dataTable.Dispose();
            }
        }
        #endregion

        #region private void RowCustomValidation(IEntityRow row)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="row"></param>
        public void RowCustomValidation(IEntityRow row)
        {
            _customErrors.Clear();
            EntityRowValidationArgs args = new EntityRowValidationArgs(row as IEntityRow);
            OnCustomRowValidation(args);
            if (!args.IsValid)
            {
                foreach (EntityRowError error in args.Errors)
                {
                    if ((object)error.Column != null)
                    {
                        row[error.Column].ErrorMessage = error.ErrorMessage;
                        row[error.Column].ErrorCode = error.ErrorCode;
                    }
                    else
                    {
                        _customErrors.Add(error);
                    }
                }
            }
        }
        #endregion

        #region public EntityRowErrorCollection GetErrors()
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public EntityRowErrorCollection GetErrors()
        {
            EntityRowErrorCollection errors = new EntityRowErrorCollection();
            foreach (IEntityRow row in this.Rows)
            {
                RowCustomValidation(row);
                foreach (EntityRowError customError in _customErrors)
                {
                    errors.Add(customError);
                }
                EntityRowErrorCollection rowErrors = (row as EntityRowBase<TEntity, TEntityRow>).GetErrors();
                foreach (EntityRowError error in rowErrors)
                {
                    if (!errors.Contains(error.ErrorMessage))
                    {
                        errors.Add(error);
                    }
                }
            }
            return errors;
        }
        #endregion

        #region public virtual void OnCustomRowValidation(EntityRowValidationArgs e)
        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        public virtual void OnCustomRowValidation(EntityRowValidationArgs e)
        {
            if (CustomRowValidation != null)
            {
                CustomRowValidation(this, e);
            }
        }

        EntityColumn[] IEntity.Columns
        {
            get { return _columns.ToArray<EntityColumn>(); }
        }

        IEntityRow[] IEntity.GetRows()
        {
            List<IEntityRow> _list = new List<IEntityRow>();
            foreach (IEntityRow row in Rows)
            {
                _list.Add(row);
            }
            return _list.ToArray();
        }

        #endregion

        #region public void Dispose()
        /// <summary>
        /// Releases the resources used by the <b>EntityBase&lt;TEntity, TEntityRow&gt;</b>.
        /// </summary>
        public void Dispose()
        {
            //this.DataTable.DataSet.Dispose();
            _dataTable.Dispose();
        }
        #endregion

        #region private string GetTableName()
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private string GetTableName()
        {
            foreach (object attribute in _child.GetCustomAttributes(true))
            {
                if (attribute is EntityAttribute)
                {
                    return (attribute as EntityAttribute).TableName;
                }
            }
            return string.Empty;
        }
        #endregion

        #region IEntity Members

        private EntityOrder _defaultOrder;
        public virtual EntityOrder DefaultOrder
        {
            get { return _defaultOrder; }
            set { _defaultOrder = value; }
        }

        public void Fill()
        {
            Fill(EntityFilter.NullValue);
        }

        public void Fill(EntityFilter filter)
        {
            Fill(filter, null, null);
        }

        public void Fill(EntityFilter filter, IDbTransaction transaction)
        {
            Fill(filter, null, transaction);
        }

        public void Fill(EntityFilter filter, EntityOrder order)
        {
            Fill(filter, order, null);
        }

        public void Fill(EntityFilter filter, EntityOrder order, IDbTransaction transaction)
        {
            _dataTable.Clear();
            _dataAccessService.Fill(_dataTable, filter, order == null ? _defaultOrder : null, transaction);
            OnDataTableChanged();
        }

        public void Fill(EntityFilter filter, int pageIndex, int pageSize, out long totalRowsCount)
        {
            Fill(filter, null, pageIndex, pageSize, out totalRowsCount, null);
        }

        public void Fill(EntityFilter filter, int pageIndex, int pageSize, out long totalRowsCount, IDbTransaction transaction)
        {
            Fill(filter, null, pageIndex, pageSize, out totalRowsCount, transaction);
        }

        public void Fill(EntityFilter filter, EntityOrder order, int pageIndex, int pageSize, out long totalRowsCount)
        {
            Fill(filter, order, pageIndex, pageSize, out totalRowsCount, null);
        }

        public void Fill(EntityFilter filter, EntityOrder order, int pageIndex, int pageSize, out long totalRowsCount, IDbTransaction transaction)
        {
            _dataTable.Clear();
            _dataAccessService.Fill(_dataTable, filter, order == null ? _defaultOrder : null, pageIndex, pageSize, out totalRowsCount, transaction);
            OnDataTableChanged();
        }

        public void Fill(StoredProcedureDefinition spDefinition)
        {
            Fill(spDefinition, null, null);
        }

        public void Fill(StoredProcedureDefinition spDefinition, IDbTransaction transaction)
        {
            Fill(spDefinition, null, transaction);
        }

        public void Fill(StoredProcedureDefinition spDefinition, EntityOrder order)
        {
            Fill(spDefinition, order, null);
        }

        public virtual void Fill(StoredProcedureDefinition spDefinition, EntityOrder order, IDbTransaction transaction)
        {
            _dataTable.Clear();
            _dataAccessService.Fill(_dataTable, spDefinition, order == null ? _defaultOrder : null, transaction);
            OnDataTableChanged();
        }

        public long Count()
        {
            return Count(null);
        }

        public long Count(EntityFilter filter)
        {
            return _dataAccessService.Count(_dataTable, filter);
        }

        public void Update()
        {
            _dataAccessService.Update(_dataTable, _columns.ToList<EntityColumn>(), null);
        }

        public void Update(IDbTransaction transaction)
        {
            _dataAccessService.Update(_dataTable, _columns.ToList<EntityColumn>(), transaction);
        }

        internal void Update(DataRow row)
        {
            Update(row, null);
        }

        internal void Update(DataRow row, IDbTransaction transaction)
        {
            _dataAccessService.Update(row, _columns.ToList<EntityColumn>(), transaction);
        }

        #endregion

        #region public void OnDataTableChanged()
        /// <summary>
        /// 
        /// </summary>
        public void OnDataTableChanged()
        {
            Refresh();
            if (DataTableChanged != null)
            {
                DataTableChanged(this, EventArgs.Empty);
            }
        }
        #endregion

        #region Web

        private Page _webForm;
        public Page WebForm
        {
            get { return _webForm; }
        }

        void _webForm_Load(object sender, EventArgs e)
        {
        }

        void _webForm_PreRender(object sender, EventArgs e)
        {
            RegisterRowPrototypeJS();
            RegisterEntityPrototypeJS();
        }


        private void RegisterEntityPrototypeJS()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine(string.Format("function {0}()", _entityName));
            sb.AppendLine("{");
            sb.AppendLine("}");
            //foreach (EntityColumn col in _columns)
            //{
            //    sb.AppendLine(string.Format("this.{1}='someValue';", _name, col.Name));
            //}
            sb.AppendLine(string.Format("{0}.Rows = new Array();", _entityName));

            for (int i = 0; i < Rows.Count; i++)
            {
                string arr = "new Array(";
                for (int j = 0; j < _columns.Count; j++)
                {
                    object val = (Rows[i] as IEntityRow)[j].Value;
                    if (val == null || (val != null && val == DBNull.Value))
                    {
                        val = "null";
                    }
                    else if (val is string || val is DateTime)
                    {
                        val = string.Format("'{0}'", val);
                    }
                    arr += val.ToString();
                    if (j != _columns.Count - 1)
                    {
                        arr += ", ";
                    }
                }
                arr += ")";
                sb.AppendLine(string.Format("{0}.Rows[{1}] = new {0}Row({2});", _entityName, i, arr));
            }

            _webForm.ClientScript.RegisterClientScriptBlock(this.GetType(), _entityName, sb.ToString(), true);
        }

        private void RegisterRowPrototypeJS()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine(string.Format("function {0}Row(values) {{", _entityName));
            for (int i = 0; i < _columns.Count; i++)
            {
                sb.AppendLine(string.Format("  this.{0} = values[{1}];", _columns[i].Name, i));
            }
            sb.AppendLine("}");
            _webForm.ClientScript.RegisterClientScriptBlock(this.GetType(), _entityName + "Row", sb.ToString(), true);
        }

        private void RegisterColumnPrototypeJS(IDictionary properties)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine(string.Format("function {0}Column(values) {{", _entityName));
            for (int i = 0; i < _columns.Count; i++)
            {
                sb.AppendLine(string.Format("  this.{0} = values[{1}];", _columns[i].Name, i));
            }
            sb.AppendLine("}");
            _webForm.ClientScript.RegisterClientScriptBlock(this.GetType(), _entityName + "Row", sb.ToString(), true);
        }
        #endregion

        #region IEntity Members



        #endregion
    }
}
